/**
 * 
 */
package org.metaverse.restful;

import java.io.StringWriter;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import org.metaverse.warehouse.Senary.Slot;
import org.metaverse.warehouse.com.Conjunction;
import org.metaverse.warehouse.com.Constraint;
import org.metaverse.warehouse.com.Disjunction;
import org.metaverse.warehouse.com.Equals;
import org.metaverse.warehouse.com.Greater;
import org.metaverse.warehouse.com.Junction;
import org.metaverse.warehouse.com.Less;
import org.metaverse.warehouse.com.Like;
import org.metaverse.warehouse.com.NotEquals;
import org.metaverse.warehouse.com.Predicate;
import org.metaverse.warehouse.com.ValueConstraint;
import org.metaverse.warehouse.com.Variable;
import org.metaverse.warehouse.com.util.BasicConstraint;
import org.metaverse.warehouse.com.util.ConstraintImpl;
import org.metaverse.warehouse.rdf.Converter;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;

/**
 * @author vjache
 *
 */
public class ConstraintJSONFormat {
	private static final String LAT = "lat";
	private static final String USR = "u";
	private static final String CTX = "c";
	private static final String OBJ = "o";
	private static final String PRED = "p";
	private static final String SUBJ = "s";
	
	private static final String AND = "and";
	private static final String OR = "or";
	private static final String EQ = "eq";
	private static final String GR = "gr";
	private static final String LS = "ls";
	private static final String LIKE = "like";
	private static final String NE = "ne";
	
	private static final String URI = "uri";
	private static final String LIT = "lit";
	private static final String DT = "dt";
	private static final String LANG = "lang";
	/**
	 * 
	 */
	private ConstraintJSONFormat() {}
	
	private static class ValueConstraintJSONFormat implements ValueConstraint.Visitor
	{		
		private final JSONWriter _writer;
		
		public ValueConstraintJSONFormat(JSONWriter aWriter)
		{_writer = aWriter;}

		@Override
		public void on(Conjunction aAnd) {
			String junctName = AND;
			onJ(aAnd, junctName);
		}

		private void onJ(Junction aJunct, String junctName) {
			try {
				_writer.object().key(junctName).array();
				for(ValueConstraint c:aJunct.getValueConstraints())
				{c.visit(this);}
				_writer.endArray();
				_writer.endObject();
			} catch (JSONException e) {
				throw new RuntimeException(e);
			}
		}

		@Override
		public void on(Disjunction aOr) {
			String junctName = OR;
			onJ(aOr, junctName);
		}

		@Override
		public void on(Equals aEq) {
//			try {
//				writeValue(aEq.getValue());
//			} catch (JSONException e) {
//				throw new RuntimeException(e);
//			}
			String predName = EQ;
			onP(aEq, predName);
		}

		private void onP(Predicate aPred, String predName) {
			try {
				_writer.object().key(predName);
				writeValue(aPred.getValue());
				_writer.endObject();
			} catch (JSONException e) {
				throw new RuntimeException(e);
			}
		}

		private void writeValue(Value value) throws JSONException {
			if(value instanceof URI)
			{
				URI uri = (URI)value;
				_writer
					.object()
						.key(URI)
						.value(uri.stringValue())
					.endObject();
			}
			else if(value instanceof BNode)
			{
				throw new UnsupportedOperationException("Blank node not acceptable");
			}
			else if(value instanceof Literal)
			{
				Literal lit = (Literal)value;
				_writer
					.object()
						.key(LIT)
						.value(lit.stringValue());
				URI datatype = lit.getDatatype();
				if(datatype!=null)
				{
					_writer
						.key(DT)
						.value(datatype.stringValue());
				}
				else
				{
					String language = lit.getLanguage();
					if(language!=null && language.length()>0)
						_writer
							.key(LANG)
							.value(language);
				}
				_writer.endObject();
			}
		}

		@Override
		public void on(Greater aGr) {
			String predName = GR;
			onP(aGr, predName);
		}

		@Override
		public void on(Less aLs) {
			String predName = LS;
			onP(aLs, predName);
		}

		@Override
		public void on(Like aLk) {
			String predName = LIKE;
			onP(aLk, predName);
		}

		@Override
		public void on(NotEquals aNe) {
			String predName = NE;
			onP(aNe, predName);
		}

		@Override
		public void on(Variable aVar) {
			// TODO Auto-generated method stub
			
		}
		
		public void write(ValueConstraint aValConstr)
		{
			aValConstr.visit(this);
		}
	}
	
	public static String toJSON(Constraint aConstraint) throws JSONException
	{
		StringWriter stringWriter = new StringWriter();
		JSONWriter jsonWriter = new JSONWriter(stringWriter);
		ValueConstraintJSONFormat vcToJsonWriter = new ValueConstraintJSONFormat(jsonWriter);
		Map<Slot, ValueConstraint> map = aConstraint.getAll();
		jsonWriter.object();
		for(Entry<Slot, ValueConstraint> ent:map.entrySet())
		{
			ValueConstraint value = ent.getValue();
			if(value==null)
				continue;
			switch(ent.getKey())
			{
			case Subject:jsonWriter.key(SUBJ);break;
			case Predicate:jsonWriter.key(PRED);break;
			case Object:jsonWriter.key(OBJ);break;
			case Context:jsonWriter.key(CTX);break;
			case User:jsonWriter.key(USR);break;
			case LastAssertTime:jsonWriter.key(LAT);break;
				
			}
			vcToJsonWriter.write(value);
		}
		jsonWriter.endObject();
		return stringWriter.toString();
	}
	
	public static Constraint fromJSON(String aJsonRep,ValueFactory aValueFactory) throws JSONException
	{
		JSONObject jObj = new JSONObject(aJsonRep);
		return fromJSON(jObj, aValueFactory);
	}

	@SuppressWarnings("unchecked")
	public static Constraint fromJSON(JSONObject jObj,
			ValueFactory aValueFactory) throws JSONException {
		ConstraintImpl c = new ConstraintImpl();
		Iterator<String> keys = jObj.keys();
		while(keys.hasNext())
		{
			String key = keys.next();
			JSONObject expr = jObj.getJSONObject(key);
			ValueConstraint ex = null;
			Iterator<String> keys2 = expr.keys();
			if(keys2.hasNext())
			{
				String key2 = keys2.next();
				ex = expression(key2,expr.get(key2),aValueFactory);
			}
			
			if(SUBJ.equals(key))
			{
				c.setSubject(ex);
			}
			else if(PRED.equals(key))
			{
				c.setPredicate(ex);
			}
			else if(OBJ.equals(key))
			{
				c.setObject(ex);
			}
			else if(CTX.equals(key))
			{
				c.setContext(ex);
			}
			else if(USR.equals(key))
			{
				c.setUser(ex);
			}
			else if(LAT.equals(key))
			{
				c.setLastAssertTime(ex);
			}
		}
		return c;
	}

	private static ValueConstraint expression(String key, Object object, ValueFactory aValueFactory) throws JSONException {
		if(EQ.equals(key))
		{
			JSONObject jsonObject = (JSONObject)object;
			return new Equals(valueOf(jsonObject,aValueFactory));
		}
		else if(GR.equals(key))
		{
			JSONObject jsonObject = (JSONObject)object;
			return new Greater(valueOf(jsonObject,aValueFactory));
		}
		else if(LIKE.equals(key))
		{
			JSONObject jsonObject = (JSONObject)object;
			return new Like(valueOf(jsonObject,aValueFactory));
		}
		else if(LS.equals(key))
		{
			JSONObject jsonObject = (JSONObject)object;
			return new Less(valueOf(jsonObject,aValueFactory));
		}
		else if(OR.equals(key))
		{
			ValueConstraint[] constrs = constraintArray(object,aValueFactory);
			return new Disjunction(constrs);
		}
		else if(AND.equals(key))
		{
			ValueConstraint[] constrs = constraintArray(object,aValueFactory);
			return new Conjunction(constrs);
		}
		else if(NE.equals(key))
		{
			JSONObject jsonObject = (JSONObject)object;
			return new NotEquals(valueOf(jsonObject,aValueFactory));
		}
		
		return null;
	}

	@SuppressWarnings("unchecked")
	private static ValueConstraint[] constraintArray(Object object, ValueFactory aValueFactory)
			throws JSONException {
		JSONArray jsonArray = (JSONArray)object;
		ValueConstraint[] constrs = new ValueConstraint[jsonArray.length()];
		for(int i=0;i<jsonArray.length();i++)
		{
			JSONObject jsonObject = jsonArray.getJSONObject(i);
			Iterator<String> keys = jsonObject.keys();
			if(keys.hasNext())
			{
				String key2 = keys.next();
				constrs[i] = expression(key2, jsonObject.get(key2),aValueFactory);
			}
		}
		return constrs;
	}

	private static Value valueOf(JSONObject jsonObject, ValueFactory aValueFactory) throws JSONException {
//		final Iterator<String> keys = jsonObject.keys();
//		final String key = keys.next();
//		String lexicalFrom = jsonObject.getString(key);
		if(jsonObject.has(URI))
		{
			return aValueFactory.createURI(jsonObject.getString(URI));
		}
		else if(jsonObject.has(LIT))
		{
			final String lexicalFrom = jsonObject.getString(LIT);
			if(jsonObject.has(DT))
			{
				String dt = jsonObject.getString(DT).trim();
				if(dt.length()>0)
				{
					final URI dtUri = aValueFactory.createURI(dt);
					return aValueFactory.createLiteral(lexicalFrom,dtUri);
				}
			}
			else
			{
				
				if(jsonObject.has(LANG))
				{
					String lang = jsonObject.getString(LANG).trim();
					if(lang.length()>0)
					{
						return aValueFactory.createLiteral(lexicalFrom,lang);
					}
				}
				else
					return aValueFactory.createLiteral(lexicalFrom);
			}
		}
		throw new IllegalArgumentException(
				String.format(
						"Unable to extract RDF value from JSON object '%s'.",
						jsonObject));
	}

	public static void main(String[] args) throws JSONException {
		ValueFactoryImpl vf = new ValueFactoryImpl();
		BasicConstraint constr = new BasicConstraint()
			.bySubject(vf.createURI("http://localhot#rsc-1"))
			.byContext(vf.createURI("http://localhot#ctx-0"))
			.byLastAssertTime(new Greater(Converter.valueOf(new Date(), vf)));
		
		String json = ConstraintJSONFormat.toJSON(constr);
		System.out.println(json);
		System.out.println(ConstraintJSONFormat.fromJSON(json,vf).getAll());
	}
}
